package com.github.lguipeng.library.animcheckbox;

import ohos.aafwk.ability.OnClickListener;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import static ohos.agp.animation.Animator.CurveType.ACCELERATE;

/**
 * Created by lgp on 2015/10/5.
 */

public class AnimCheckBox extends ComponentContainer implements Component.DrawTask, Component.EstimateSizeListener, ComponentContainer.ArrangeListener, Component.TouchEventListener {
    private final double mSin27 = Math.sin(Math.toRadians(27));
    private final double mSin63 = Math.sin(Math.toRadians(63));
    private Paint mPaint = new Paint();
    private final static int mDuration = 500;
    private final static int defaultSize = 40;
    private int radius;
    private RectFloat mRectF = new RectFloat();
    private RectFloat mInnerRectF = new RectFloat();
    private Path mPath = new Path();
    private float mSweepAngle;
    private float mHookStartY;
    private float mBaseLeftHookOffset;
    private float mBaseRightHookOffset;
    private float mEndLeftHookOffset;
    private float mEndRightHookOffset;
    private int size;
    private boolean mChecked;
    private float mHookOffset;
    private float mHookSize;
    private float mInnerCircleAlpha = 1.0f;
    private int mStrokeWidth = 2;
    private static final Color M_STROKE_COLOR = Color.BLUE;
    private Color mStrokeColor;
    private static final Color M_CIRCLE_COLOR = Color.WHITE;
    private Color mCircleColor;
    private OnCheckedChangeListener mOnCheckedChangeListener;

    private int dip(int dip) {
        final float scale = AttrHelper.getDensity(getContext());
        return (int) (scale * dip);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        return false;
    }

    public AnimCheckBox(Context context) {
        this(context, null);
    }

    public AnimCheckBox(Context context, AttrSet attrs) {
        super(context, attrs);
        // 初始化
        init(context, attrs);
        // 设置测量组件的侦听器
        setEstimateSizeListener(this);
        //设置容器组件布局子组件的侦听器
        setArrangeListener(this);
        // 添加绘制任务
        addDrawTask(this);
    }

    /**
     * 获取自定义属性
     *
     * @param
     */
    private void init(Context context, AttrSet attrs) {
        boolean checked = mChecked;
        if (attrs != null) {
            mStrokeWidth = attrs.getAttr("stroke_width").isPresent() ? attrs.getAttr("stroke_width").get().getDimensionValue() : mStrokeWidth;
            mStrokeColor = attrs.getAttr("stroke_color").isPresent() ? attrs.getAttr("stroke_color").get().getColorValue() : M_STROKE_COLOR;
            mCircleColor = attrs.getAttr("circle_color").isPresent() ? attrs.getAttr("circle_color").get().getColorValue() : M_CIRCLE_COLOR;
            checked = attrs.getAttr("checked").isPresent() ? attrs.getAttr("checked").get().getBoolValue() : false;
        } else {
            mStrokeWidth = dip(mStrokeWidth);
        }
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mStrokeColor);
        super.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                setChecked(!mChecked);
            }
        });
        setCheckedViewInner(checked, false);
    }

    /**
     * 测量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     * @return
     */
    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = EstimateSpec.getSize(widthMeasureSpec);
        int height = EstimateSpec.getSize(heightMeasureSpec);
        if (EstimateSpec.getMode(widthMeasureSpec) == EstimateSpec.NOT_EXCEED &&
                EstimateSpec.getMode(heightMeasureSpec) == EstimateSpec.NOT_EXCEED) {
            LayoutConfig params = getLayoutConfig();
            width = height;
            height = Math.min(dip(defaultSize) - params.getMarginLeft() - params.getMarginRight(),
                    dip(defaultSize) - params.getMarginBottom() - params.getMarginTop());
        }
        int size = Math.min(width - getPaddingLeft() - getPaddingRight(),
                height - getPaddingBottom() - getPaddingTop());
        setEstimatedSize(size, size);
        return true;
    }

    /**
     * 计算
     */
    @Override
    public boolean onArrange(int left, int top, int right, int bottom) {
        size = getWidth();
        radius = (getWidth() - (2 * mStrokeWidth)) / 2;
        RectFloat mRectF2 = new RectFloat(mStrokeWidth, mStrokeWidth, size - mStrokeWidth, size - mStrokeWidth);
        mRectF.fuse(mRectF2);
        mInnerRectF.fuse(mRectF);
        mInnerRectF.setPivot(mStrokeWidth / 2, mStrokeWidth / 2);
        mHookStartY = (float) (size / 2 - (radius * mSin27 + (radius - radius * mSin63)));
        mBaseLeftHookOffset = (float) (radius * (1 - mSin63)) + mStrokeWidth / 2;
        mBaseRightHookOffset = 0f;
        mEndLeftHookOffset = mBaseLeftHookOffset + (2 * size / 3 - mHookStartY) * 0.33f;
        mEndRightHookOffset = mBaseRightHookOffset + (size / 3 + mHookStartY) * 0.38f;
        mHookSize = size - (mEndLeftHookOffset + mEndRightHookOffset);
        mHookOffset = mChecked ? mHookSize + mEndLeftHookOffset - mBaseLeftHookOffset : 0;
        return false;
    }

    /**
     * 绘制
     *
     * @param component
     * @param canvas
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawCircle(canvas);
        drawHook(canvas);

    }

    private void drawCircle(Canvas canvas) {
        initDrawStrokeCirclePaint();
        canvas.drawArc(new RectFloat(mRectF), new Arc(202, mSweepAngle, false), mPaint);
        initDrawAlphaStrokeCirclePaint();
        canvas.drawArc(new RectFloat(mRectF), new Arc(202, mSweepAngle - 360, false), mPaint);
        initDrawInnerCirclePaint();
        canvas.drawArc(new RectFloat(mRectF), new Arc(0, 360, false), mPaint);
    }

    private void drawHook(Canvas canvas) {
        if (mHookOffset == 0) {
            return;
        }
        initDrawHookPaint();
        mPath.reset();
        float offset;
        if (mHookOffset <= (2 * size / 3 - mHookStartY - mBaseLeftHookOffset)) {
            mPath.moveTo(mBaseLeftHookOffset, mBaseLeftHookOffset + mHookStartY);
            mPath.lineTo(mBaseLeftHookOffset + mHookOffset, mBaseLeftHookOffset + mHookStartY + mHookOffset);
        } else if (mHookOffset <= mHookSize) {
            mPath.moveTo(mBaseLeftHookOffset, mBaseLeftHookOffset + mHookStartY);
            mPath.lineTo(2 * size / 3 - mHookStartY, 2 * size / 3);
            mPath.lineTo(mHookOffset + mBaseLeftHookOffset,
                    2 * size / 3 - (mHookOffset - (2 * size / 3 - mHookStartY - mBaseLeftHookOffset)));
        } else {
            offset = mHookOffset - mHookSize;
            mPath.moveTo(mBaseLeftHookOffset + offset, mBaseLeftHookOffset + mHookStartY + offset);
            mPath.lineTo(2 * size / 3 - mHookStartY, 2 * size / 3);
            mPath.lineTo(mHookSize + mBaseLeftHookOffset + offset,
                    2 * size / 3 - (mHookSize - (2 * size / 3 - mHookStartY - mBaseLeftHookOffset) + offset));
        }
        canvas.drawPath(mPath, mPaint);
    }

    private void initDrawHookPaint() {
        mPaint.setAlpha(1.0f);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mStrokeColor);

    }

    private void initDrawStrokeCirclePaint() {
        mPaint.setAlpha(1.0f);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mStrokeColor);
    }

    private void initDrawAlphaStrokeCirclePaint() {
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setColor(mStrokeColor);
        mPaint.setAlpha(0.3f);
    }

    private void initDrawInnerCirclePaint() {
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        mPaint.setColor(mCircleColor);
        mPaint.setAlpha(mInnerCircleAlpha);
    }

    private void startCheckedAnim() {
        AnimatorValue animator = new AnimatorValue();
        final float hookMaxValue = mHookSize + mEndLeftHookOffset - mBaseLeftHookOffset;
        final float circleMaxFraction = mHookSize / hookMaxValue;
        final float circleMaxValue = 360 / circleMaxFraction;
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                mHookOffset = v * hookMaxValue;
                if (v <= circleMaxFraction) {
                    mSweepAngle = (int) ((circleMaxFraction - v) * circleMaxValue);
                } else {
                    mSweepAngle = 0;
                }
                mInnerCircleAlpha = v;
                invalidate();
            }
        });

        animator.setCurveType(ACCELERATE);
        animator.setDuration(mDuration);
        animator.start();
    }

    private void startUnCheckedAnim() {
        AnimatorValue animator = new AnimatorValue();
        final float hookMaxValue = mHookSize + mEndLeftHookOffset - mBaseLeftHookOffset;
        final float circleMinFraction = (mEndLeftHookOffset - mBaseLeftHookOffset) / hookMaxValue;
        final float circleMaxValue = 360 / (1 - circleMinFraction);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                float circleFraction = v;
                float fraction = 1 - circleFraction;
                mHookOffset = fraction * hookMaxValue;
                if (circleFraction >= circleMinFraction) {
                    mSweepAngle = (int) ((circleFraction - circleMinFraction) * circleMaxValue);
                } else {
                    mSweepAngle = 0;
                }
                mInnerCircleAlpha = (int) (fraction * 1.0f);
                invalidate();
            }
        });
        animator.setCurveType(ACCELERATE);
        animator.setDuration(mDuration);
        animator.start();
    }

    private void startAnim() {
        if (mChecked) {
            startCheckedAnim();
        } else {
            startUnCheckedAnim();
        }
    }

    public boolean isChecked() {
        return mChecked;
    }

    /**
     * setChecked with Animation
     *
     * @param checked true if checked, false if unchecked
     */
    public void setChecked(boolean checked) {
        setChecked(checked, true);
    }

    public void toggle() {
        setChecked(!isChecked());
    }

    /**
     * @param checked   true if checked, false if unchecked
     * @param animation true with animation,false without animation
     */
    public void setChecked(boolean checked, boolean animation) {
        if (checked == this.mChecked) {
            return;
        }
        setCheckedViewInner(checked, animation);
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onChange(this, mChecked);
        }
    }

    /**
     * @deprecated use {@link #setOnCheckedChangeListener(OnCheckedChangeListener)} instead
     */

    @Deprecated
    public void setClickedListener(OnClickListener l) {
        //Empty!
    }

    private void setCheckedViewInner(boolean checked, boolean animation) {
        this.mChecked = checked;
        if (animation) {
            startAnim();
        } else {
            if (mChecked) {
                mInnerCircleAlpha = 1.0f;
                mSweepAngle = 0;
                mHookOffset = mHookSize + mEndLeftHookOffset - mBaseLeftHookOffset;
            } else {
                mInnerCircleAlpha = 0.0f;
                mSweepAngle = 360;
                mHookOffset = 0;
            }
            invalidate();
        }
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        this.mOnCheckedChangeListener = listener;
    }

    public interface OnCheckedChangeListener {
        void onChange(AnimCheckBox view, boolean checked);
    }

}
